// device.c
// Joseph Craig, Rebecca Rush, Steven Andryzcik
// Version 6 - 12/08/2011

#include "mpx_supt.h"
#include "device.h"
#include "common.h"
#include "inter.h"
#include "process.h"
#include <dos.h>

/****************************** Global Variables ******************************/
dcb device1 = {CLOSED};

void interrupt (*currentIntHand)() = NULL;

/**************************** Function Definitions ****************************/
//Definitions

// com_open
// Parameters: Event flag, Baud Rate
// Return: 0 if successfully opens or an error code if it fails.
// Functions called: getvect, setvect, outportb, disable, enable
// Opens a communication port 
// and creates a device control block for that port.
int com_open(int *eflag_p, int baud_rate){
	int baud_rate_divisor;
	int mask;
	
	if(eflag_p == NULL){
		print("Invalid event flag pointer.", TRUE);
		return -101;
	}
	else{
		if(
			baud_rate != 110 &&
			baud_rate != 150 &&
			baud_rate != 300 &&
			baud_rate != 600 &&
			baud_rate != 1200 &&
			baud_rate != 2400 &&
			baud_rate != 4800 &&
			baud_rate != 9600 &&
			baud_rate != 19200
		){
			print("Invalid baud rate divisor.", TRUE);
			return -102;
		}
		else{
			if(device1.flag == OPEN){ // check to see if device is not open
				print("Port is already open.", TRUE);
				return -103;
			}
			else{
				/*Initialize the DCB. Sets the device to open, 
				saves a copy of the event flag pointer, 
				and sets the intital device status to idle.
				Also initializes the ring buffer parameters */
				device1.flag = OPEN;
				device1.event_flag = eflag_p;
				device1.status_code = IDLE;
				device1.ring_buffer_in = 0;
				device1.ring_buffer_out = 0;
				device1. ring_buffer_count = 0;
				
				/* Saves current interrupt handler*/
				currentIntHand = getvect(COM1_INT_ID);
				
				/*Install new handler*/
				setvect(COM1_INT_ID, handle_port);
				// setvect(0x30, &high);
				
				/*Compute the baud rate divisor*/
				baud_rate_divisor = 115200 / (long) baud_rate;
				
				/* Store the value 0x80 in the Line Control Register. This
				 * allows the first two port addresses to access the Baud Rate
				 * Divisor register.
				 */
				outportb(COM1_LC, 0x80);
				
				/* Store the high order and low order bytes of the baud rate
				 * divisor into the MSB and LSB registers, respectively.
				 */
				outportb(COM1_BRD_LSB, baud_rate_divisor & 0xFF);
				outportb(COM1_BRD_MSB, (baud_rate_divisor >> 8) & 0xFF);
				
				/* Store the value 0x03 in the Line Control Register. This sets
				 * the line characteristics to 8 data bits, 1 stop bit, and no
				 * parity. It also restores normal functioning of the first two
				 * ports.
				 */
				outportb(COM1_LC, 0x03);
				
				/* Enable the appropriate level in the PIC mask register.
				 */
				disable();
				mask = inportb(PIC_MASK);
				mask = mask & ~0x10; //& ~0x80;
				outportb(PIC_MASK, mask);
				enable();
				
				/* Enable overall serial port interrupts by storing the value
				 * 0x08 in the Modem Control register.
				 */
				outportb(COM1_MC, 0x08);
				
				/* Enable input ready interrupts only by storing the value 0x01
				 * in the Interrupt Enable register.
				 */
				outportb(COM1_INT_EN, 0x01);
			}
		}
	}
	return 0;
}

// com_close
// Close a communication port 
// Parameters: none
// Return: 0 if successfully close or -201 if it fails.
int com_close(void){
	int mask;
	/*Check to see that the port is already open*/
	if(device1.flag == OPEN){
		/*Close the DCB*/
		device1.flag = CLOSED;
		/*Disable the appropriate level in the PIC mask register*/
		disable();
		mask = inportb(PIC_MASK);
		mask = mask | 0x10; //| 0x80;
		outportb(PIC_MASK, mask);
		enable();
		/*Load 0x00 into the Modem Status and Interrupt Enable registers*/
		outportb(COM1_MS, 0x00);
		outportb(COM1_INT_EN, 0x00);
		/*Restore the original interrupt vector*/
		setvect(COM1_INT_ID, currentIntHand);
		return 0;
	}
	else{
		print("Serial port not open", TRUE);
		return -201;
	}
}

// com_read
// Initializes the reading of data received from the serial port.  
// Parameters: Buffer Pointer, Count Pointer
// Return: 0 if successfully reads or an error code if it fails.
int com_read(char *buf_p, int *count_p){

	if(device1.flag != OPEN){
		print("Port not open.", TRUE);
		return -301;
	}
	else{
		if(device1.status_code != IDLE){
			print("Device is busy.", TRUE);
			return -304;
		}
		else{
			if(buf_p == NULL){
				print("Invalid Buffer address.", TRUE);
				return -302;
			}
			else{
				if(count_p == NULL || *count_p <= 0){
					print("Invalid count address or count value.", TRUE);
					return -303;
				}
				else{
					/*Save the address of the requestor's buffer*/
					device1.in_buff = buf_p;
					/*Save the address of the count*/
					device1.in_count = count_p;
					/*Set input done count to 0*/
					device1.in_done = 0;
					/*Clear the event flag*/
					*device1.event_flag = 0;
					/*Set status to reading*/
					device1.status_code = READING; 
					/*Disable interrupts*/
					disable();

					while(device1.ring_buffer_count != 0 && device1.in_done < *count_p){
						int index = device1.ring_buffer_out;
						
						buf_p[device1.in_done++] = device1.ring_buffer[index];
						device1.ring_buffer_out = (index +1) % BUFSIZ;
						device1.ring_buffer_count--;
						
						if(device1.ring_buffer[index] == '\r'){
							break;
						}
					}
					
					enable();
					
					if (device1.in_done >= *count_p || buf_p[device1.in_done] == '\r') {
						
						if (buf_p[device1.in_done - 1] == '\r') {
							buf_p[--device1.in_done] = '\0';
						} else {
							buf_p[device1.in_done] = '\0';
						}
						
						device1.status_code = IDLE;
						*device1.event_flag = 1;
						*count_p = device1.in_done;
					}
					
					return 0;
				}
			}
		}
	}
}

// com_write 
// Parameters: Buffer Pointer, Count Pointer
// Return: 0 if successfully opens or an error code if it fails.
// Functions called: outportb, enable, disable
// Copies data from the requestors buffer to the comport.
int com_write(char *buf_p, int *count_p){
	int mask;
	if(device1.flag != OPEN){
		print("Serial port not open", TRUE);
		return -401;
	}
	else{
		if(device1.status_code != IDLE){
			print("Device is busy", TRUE);
			return -404;
		}
		else{
			if(buf_p == NULL){
				print("Invalid Buffer Address.", TRUE);
				return -402;
			}	
			else{
				if(count_p == NULL || *count_p <= 0){
					print("Invalid count address or count value.", TRUE);
					return -403;
				}
				else if(device1.ring_buffer_count >= BUFSIZ){
					print("Buffer is full.", TRUE);
					return -405;
				}
				else{
					/*Save the address of the requestor's buffer*/
					device1.out_buff = buf_p;
					/*Save the count*/
					device1.out_count = count_p;
					/*Set the output done count to 0*/
					device1.out_done = 0;
					/*Set status to writing*/
					device1.status_code = WRITING;
					/*Clear the event flag*/
					*device1.event_flag = 0;
					/*Write the first character to the com port*/
					outportb(COM1_BASE, *device1.out_buff);
					/*Increment output done*/
					device1.out_done++;
					/*Enable the write interrupts*/
					disable();//added
					mask = inportb(COM1_INT_EN);
					mask = mask | 0x02;
					outportb(COM1_INT_EN, mask);
					enable(); //added
				}
			}
		
		}
	}
	return 0;
}

// read_in
// Parameters: none
// Return: void
// Functions called: inportb 
// Second level interrupt is called when a single character
// has been placed in the input register.
void read_in(){
	
	unsigned char in_char;
	in_char = inportb(COM1_BASE);
	// If current status is not reading, no prcess is current requesting data
	// Add to the ring buffer.
	if(device1.status_code != READING){
		// If the ring buffer is full, dicard the character and return
		if(device1.ring_buffer_count == BUFSIZ){
			in_char = NULL;
			return;
		}
		// If the ring buffer is not full, store the character in the ring buffer
		// And increment the ring buffer indexes.
		else{
			device1.ring_buffer[device1.ring_buffer_in] = in_char;
			device1.ring_buffer_count++;
			device1.ring_buffer_in = (device1.ring_buffer_in +1) % BUFSIZ;
		}
	}
	// If current status is reading - procces is currently requesting data
	else{
		// Store the charcter in the input buffer and increment the count.
		device1.in_buff[device1.in_done++] = in_char;
		// Check to see if we have finished reading by looking to see if we
		// have encountered a '\r' or the input buffer has been filled.
		if(in_char == '\r' || *device1.in_count == device1.in_done){
			// Null terminate the input buffer.
			if (in_char == '\r') {
				device1.in_buff[--device1.in_done] = '\0';
			} else {
				device1.in_buff[device1.in_done] = '\0';
			}
			// Set device status to idle
			device1.status_code = IDLE;
			// Clear the event flag
			*device1.event_flag = 1;
			// Set input count to input done, so the calling function knows
			// how many characters were placed into the buffer. 
			*device1.in_count = device1.in_done;
		}
	}
}

// write_out
// Parameters: none
// Return: void
// Functions called: outportb
// Second level interrupt that is generated after a character
// has been successfully written to the comport.
void write_out(){
	int mask;
	/*If the current status is not writing, Ignore the interrupt and return*/
	if(device1.status_code != WRITING){
		return;
	}
	else{
		// Copy the next characters to the output register 
		//and increment the appropriate counts
		outportb(COM1_BASE, device1.out_buff[device1.out_done++]);

		if (device1.out_done >= *device1.out_count) {
			//Reset the status to idel
			device1.status_code = IDLE;
			//Set the event flag to indicate completion of an operation
			*device1.event_flag = 1;
			//Disable interrupts 
			mask = inportb(COM1_INT_EN);
			mask = mask & ~0x02;
			outport(COM1_INT_EN, mask);
		}
	}

}